#include "message_passing.h"

/* builds a message and returns it */
int32_t build_message(PCB* from, int32_t to, int32_t data, int32_t priority, int8_t data_flag, message* mess){
  message_set_from(mess, pcb_get_pid(from));
  message_set_to(mess, to);
  switch(data_flag){
  case INT_DATA:
    message_set_int_value(mess, data);
    break;
  case STR_DATA:
    if(message_set_str_value(mess, (char*)data, to) == OUT_OF_MEMORY)
      return OUT_OF_MEMORY;
    break;
  case PINFO_DATA:
    message_set_pinfo_values(mess,pcb_get_priority(from),pcb_get_program_id(from), pcb_get_parent(from), pcb_get_status(from));
    break;
  default:
    ;
  }
  message_set_data_flag(mess, data_flag);
  message_set_priority(mess, priority);
  return SUCCESS;
}

/* sends a message with data in it to the process identified by pid and returns SUCCESS if the operation has been done successfully, OUT_OF_MEMORY, WRONG_PRIORITY or FAILURE otherwise */ 
int32_t send_integer(int32_t to, int32_t data, int32_t priority ){
  message* mess;
  PCB* pcb = get_process_by_pid(to);
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  if(priority < 1)
    return WRONG_PRIORITY; //a message priority must be at least 1
  if(pcb != NULL){
    mess = malloc(sizeof(message),to); //the owner of this memory is to, so that the receiver can free it 
    if(mess == NULL)
      return OUT_OF_MEMORY;
    build_message(caller,to,data,priority,INT_DATA,mess);
    return send_pcb(pcb, mess);
  }
  return FAILURE;
}

/* sends a message with data in it to the process identified by pid and returns SUCCESS if the operation has been done successfully, OUT_OF_MEMORY or FAILURE otherwise */ 
int32_t send_string(int32_t to, char* data, int32_t priority ){
  message* mess;
  PCB* pcb = get_process_by_pid(to);
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  if(priority < 1)
    return WRONG_PRIORITY; //a message priority must be at least 1
  if(pcb != NULL){
    mess = malloc(sizeof(message),to);
    if(mess == NULL)
      return OUT_OF_MEMORY;
    if(build_message(caller,to,(int32_t)data,priority,STR_DATA,mess) == OUT_OF_MEMORY){
      free(mess, SYSTEM_ID);
      return OUT_OF_MEMORY;
    }
    return send_pcb(pcb,mess);
  }
  return FAILURE;
}

/* sends a message with data in it to the process identified by pid and returns SUCCESS if the operation has been done successfully, OUT_OF_MEMORY or FAILURE otherwise */ 
int32_t send_process_information(int32_t to, int32_t priority){
  message* mess;
  PCB* pcb = get_process_by_pid(to);
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  if(priority < 1)
    return WRONG_PRIORITY; //a message priority must be at least 1
  if(pcb != NULL){
    mess = malloc(sizeof(message),to);
    if(mess == NULL)
      return OUT_OF_MEMORY;
    build_message(caller,to,0,priority,PINFO_DATA,mess);
    return send_pcb(pcb,mess);
  }
  return FAILURE;
}

/* sends the message mess to pcb */
int32_t send_pcb(PCB* pcb, message* mess){
  if(pcb == NULL)
    return FAILURE;
  message_queue* m_queue = pcb_get_message_queue(pcb);
  //we build the message and insert it
  if(insert_message(m_queue, mess) == SUCCESS) {
    if(wake_me_up(m_queue, mess)){
      //the new message has to wake the receiver up
      wake_up_pcb(pcb);
      //reset the to's message queue status
      m_queue->status = NOT_WAITING;
    }
    return SUCCESS;
  }
  //the insertion failed
  return OUT_OF_MEMORY;
}


/* returns the first message and removes it from the queue. Blocking call if the queue is empty. */
int32_t receive_first(message** mess){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_first(m_queue, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FIRST,0);
    if(block_pcb(caller) == FAILURE)
      return FAILURE; //fatal error
    return MESSAGE_NOT_FOUND; //message not found at this time
  }
  return SUCCESS;
}

/* returns the first message sent by from and removes it from the queue. Blocking call if there is no such message */
int32_t receive_from(int32_t from, message** mess){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_from(m_queue, from, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FROM, from);
    if(block_pcb(caller) == FAILURE){
      return FAILURE;
    }
    return MESSAGE_NOT_FOUND;
  }
  return SUCCESS;
}

/* returns the first message with the correct priority and removes it from the queue. Blocking call if there is no such message */
int32_t receive_priority(int32_t priority,message** mess){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_priority(m_queue, priority, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_PRIORITY, priority);
    if(block_pcb(caller) == FAILURE)
      return FAILURE;
    return MESSAGE_NOT_FOUND;
  }
  return SUCCESS;
}

/* gets the first message. If none is available, waits at most timeout ms before returning. It also returns as soon as a message arrives. Returns SUCCESS if a message has arrived, MESSAGE_NOT_FOUND otherwise and FAILURE in case of fatal error.*/
int32_t receive_first_timeout(message** mess, uint32_t timeout){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_first(m_queue, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FIRST,0);
    if(timeout) //when timeout is 0, it indicates the second check. No delay there.
      if(delay_pcb(caller,timeout) == FAILURE)
	return FAILURE; //fatal error
    //if timeout is not 0, indicates that the process is delayed. Otherwise, indicates that no correct message has arrived during the specified time
    return MESSAGE_NOT_FOUND; 
  }
  return SUCCESS;
}

/*  gets the first message from from. It none is available, waits at most timeout ms before returning. It returns when a correct message arrives or at the end of the given time, whichever comes first. Returns SUCCESS if a message has arrived or MESSAGE_NOT_FOUND if the caller is delayed. */
int32_t receive_from_timeout(int32_t from, message** mess, uint32_t timeout){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_from(m_queue, from, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_FROM, from);
    if(timeout)
      if(delay_pcb(caller,timeout) == FAILURE)
	return FAILURE;
    return MESSAGE_NOT_FOUND;
  }
  return SUCCESS;
}

/*  gets the first message with the correct priority. It none is available, waits at most timeout ms before returning. It returns when a correct message arrives or at the end of the given time, whichever comes first. Returns SUCCESS if a message has arrived or MESSAGE_NOT_FOUND if the caller is delayed. */
int32_t receive_priority_timeout(int32_t priority, message** mess, uint32_t timeout){
  PCB* caller = get_running();
  if(caller == NULL)
    return FAILURE;
  //we get the queue to receive from
  message_queue* m_queue = pcb_get_message_queue(caller);
  if(get_message_priority(m_queue, priority, mess) == FAILURE){
    //a correct message has not been found, we set the status information of the message queue and block the process
    set_status_information(m_queue,WAITING_PRIORITY, priority);
    if(timeout)
      if(delay_pcb(caller,timeout) == FAILURE)
	return FAILURE;
    return MESSAGE_NOT_FOUND;
  }
  return SUCCESS;
}

/* returns the number of messags in the queue of the PCB pcb */
int32_t get_number_messages(PCB* pcb){
  return message_queue_get_count(pcb_get_message_queue(pcb));
}

